/***
 * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package br.com.caelum.vraptor.proxy;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import javassist.util.proxy.MethodFilter;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.com.caelum.vraptor.ioc.ApplicationScoped;

/**
 * Javassist implementation for {@link Proxifier}.
 * 
 * @author Otávio Scherer Garcia
 * @since 3.3.1
 */
@ApplicationScoped
public class JavassistProxifier
	implements Proxifier {

	private static final Logger logger = LoggerFactory.getLogger(JavassistProxifier.class);

	/**
	 * Methods like toString and finalize will be ignored.
	 */
	private static final List<Method> OBJECT_METHODS = Arrays.asList(Object.class.getDeclaredMethods());

	/**
	 * Do not proxy these methods.
	 */
	private static final MethodFilter IGNORE_BRIDGE_AND_OBJECT_METHODS = new MethodFilter() {
	public boolean isHandled(Method method) {
		return !method.isBridge() && !OBJECT_METHODS.contains(method);
	}
	};

	private final InstanceCreator instanceCreator;

	public JavassistProxifier(InstanceCreator instanceCreator) {
	this.instanceCreator = instanceCreator;
	}

	public <T> T proxify(Class<T> type, MethodInvocation<? super T> handler) {
	final ProxyFactory factory = new ProxyFactory();
	factory.setFilter(IGNORE_BRIDGE_AND_OBJECT_METHODS);

	if (type.isInterface()) {
		factory.setInterfaces(new Class[] { type });
	} else {
		factory.setSuperclass(type);
	}

	Class<?> proxyClass = factory.createClass();

	Object proxyInstance = instanceCreator.instanceFor(proxyClass);
	setHandler(proxyInstance, handler);

	logger.debug("a proxy for {} is created as {}", type, proxyClass);

	return type.cast(proxyInstance);
	}

	public boolean isProxy(Object o) {
	return o != null && ProxyObject.class.isAssignableFrom(o.getClass());
	}
	
	private <T> void setHandler(Object proxyInstance, final MethodInvocation<? super T> handler) {
	ProxyObject proxyObject = (ProxyObject) proxyInstance;

	proxyObject.setHandler(new MethodHandler() {
		public Object invoke(final Object self, final Method thisMethod, final Method proceed, Object[] args)
		throws Throwable {

		return handler.intercept((T) self, thisMethod, args, new SuperMethod() {
			public Object invoke(Object proxy, Object[] args) {
			try {
				return proceed.invoke(proxy, args);
			} catch (Throwable throwable) {
				throw new ProxyInvocationException(throwable);
			}
			}
		});
		}
	});
	}
}
